Skip to content

Switch Grats to an ES module#219

Merged
captbaritone merged 1 commit intomainfrom
pr/2-esm
Mar 10, 2026
Merged

Switch Grats to an ES module#219
captbaritone merged 1 commit intomainfrom
pr/2-esm

Conversation

@captbaritone
Copy link
Owner

Summary

  • Add "type": "module" to package.json
  • Replace __dirname with import.meta.url + fileURLToPath in gratsRoot.ts, cli.ts, test.ts, buildConfigTypes.ts
  • Replace import { version } from "../package.json" with readFileSync-based version reading
  • Replace JSON import in configSpec.ts with createRequire approach (compatible with webpack)
  • Update build script to copy configSpecRaw.json to dist
  • Add webpack fallbacks (url, fs, module) and extensionAlias in website plugin
  • Switch website from ts-node to tsx
  • Add "type": "module" to all example project package.json files
  • Add .js extensions to relative imports in example .ts files
  • Add explicit importModuleSpecifierEnding: ".js" to example tsconfigs

Part 2 of 5 — split from #217. Builds on #218.

Test plan

  • pnpm run build passes
  • pnpm run test passes
  • pnpm run lint passes

@captbaritone captbaritone force-pushed the pr/1-js-extensions branch 2 times, most recently from 4bbad7f to 3da3d3e Compare March 10, 2026 22:28
@captbaritone captbaritone changed the base branch from pr/1-js-extensions to main March 10, 2026 22:38
@netlify
Copy link

netlify bot commented Mar 10, 2026

Deploy Preview for grats ready!

Name Link
🔨 Latest commit b2fc5d4
🔍 Latest deploy log https://app.netlify.com/projects/grats/deploys/69b0ad31563cf300082e5632
😎 Deploy Preview https://deploy-preview-219--grats.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@captbaritone captbaritone force-pushed the pr/2-esm branch 2 times, most recently from a9211d7 to 2a98eab Compare March 10, 2026 22:45
const pkg = JSON.parse(readFileSync(resolve(__dirname, relPath), "utf8"));
if (pkg.name === "grats") return pkg.version;
} catch {
// Ignore missing/unreadable package.json files
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we console.error here and ask folks to report a bug?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added console.error with a link to the issues page.

import { ConfigSpec } from "./gratsConfig.js";

// Use createRequire to load JSON in ESM without `import ... with { type: "json" }`
// which is not supported by webpack's babel-loader (used by the playground).
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there not some way to find a workaround in the docusorus/webpack?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Switched to import ... from "./configSpecRaw.json" with { type: "json" } which is the standard ESM JSON import syntax. Webpack 5.97+ supports import attributes natively. Added @babel/plugin-syntax-import-attributes to the website babel config so babel-loader handles it too. No more createRequire workaround, and the cp in the build script is gone since tsc --build copies the JSON automatically with resolveJsonModule.

@@ -1,4 +1,5 @@
import { version as gratsTsVersion } from "typescript";
// @ts-ignore — typescript/lib/tsserverlibrary doesn't resolve under ESM + nodenext
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no good. Think this means we lose all typing of TS in this file, right? Why should this change as part of moving to es modules?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The issue was that typescript/lib/tsserverlibrary does not resolve under ESM + nodenext. The fix is much simpler: just import from "typescript" directly — the main module exports all the same types (TS.server.PluginCreateInfo, TS.LanguageService, etc.). No @ts-ignore needed, full type safety preserved.

"name": "grats-example-apollo-server",
"version": "0.0.0",
"description": "Example server showcasing Grats used with Apollo Server",
"type": "module",
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we're migrating every example project to es modules. Are we really forcing all of our users to upgrade? Ideally this PR is as minimally invasive as possible and the examples could demonstrate that most consumers will NOT need to make changes to upgrade to this version.

If we really are forcing all users to upgrade or do some other workaround, maybe we need an example cjs project that shows how to achieve that wrokaround?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this is unavoidable. With grats now being an ESM package ("type": "module"), TypeScript in CJS mode with module: "NodeNext" raises TS1479 even for import type from an ESM package. So any user on module: "NodeNext" (the recommended Node.js setting) needs "type": "module" in their package.json. Users with bundler-based setups (webpack/vite with moduleResolution: "bundler") don't need to change anything since bundler resolution doesn't enforce the CJS/ESM boundary.

package.json Outdated
"test": "tsx src/tests/test.ts",
"test-example-projects": "node src/tests/testExampleProjects.mjs",
"build": "rm -rf dist/ && tsc --build",
"build": "rm -rf dist/ && tsc --build && cp src/configSpecRaw.json dist/src/configSpecRaw.json",
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above comment about webpack. Would love to avoid this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. With the switch to import ... with { type: "json" }, tsc --build copies the JSON automatically (via resolveJsonModule), so the manual cp is no longer needed.

Add `"type": "module"` to package.json and update all CJS patterns
to ESM equivalents:
- Replace `__dirname` with `import.meta.url` + `fileURLToPath`
- Replace `import { version } from "../package.json"` with readFileSync
- Replace JSON import in configSpec.ts with `createRequire`
- Update build script to copy configSpecRaw.json to dist
- Add webpack fallbacks for `url`, `fs`, `module` in website plugin
- Add `extensionAlias` to webpack config for .js→.ts resolution
- Update all example projects for ESM compatibility
- Switch website from ts-node to tsx
@captbaritone captbaritone merged commit db08606 into main Mar 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant